/*
 *  ckr_spec_between_comparisons.cpp
 *  ckrSpec
 *
 *  Created by C. Keith Ray on 2006.11.05.
 *  Copyright 2006 __MyCompanyName__. All rights reserved.
 *
 */

#import "ckr_spec_cpp_specs.h"
#import "ckr_spec_cpp.h"

#include <iostream>
#include <sstream>
#include <vector>
#include <deque>

#include <assert.h>

using namespace std;
using namespace ckr;

/* I created 'between', 'within', and 'close_to' specifically for
floating-point comparisons, because floating-point values are rarely
exact, especially if many computations are involved. 

Because of this lack of exact representations, it is dangerous 
try to use "SHOULD_EQUAL" on float or double values. 

For example, the decimal value 0.9 may not be not represented exactly in some
implementations of "float" and "double". 

In the specification

	VALUE(aValue).SHOULD_EQUAL(0.9);

where aValue is computed and should be approximately equal to 0.9, SHOULD_EQUAL will 
rarely pass its check even though aValue may be so close to the value 0.9 that 
printing it will display it as 0.9; it might actually be 0.90000001 or 0.89999999.

Using between/within/close_to, we can more successfully say:

	VALUE(aValue).SHOULD_BE_BETWEEN( 0.9001, 0.8999 );
or
	VALUE(aValue).SHOULD_BE_WITHIN( 0.9001, 0.8999 );
or
	VALUE(aValue).SHOULD_BE_CLOSE_TO( 0.9, 0.0001 ); // second number is the 'delta' value.

and get the desired checks passing or failing.

Remember, even this might fail on some systems:

	VALUE(9.0/10.0).SHOULD_EQUAL(0.9);

and recall that 9/10 (no decimal points) is an operation on integer values; 9/10 equals zero.

I've also implemented BETWEEN and WITHIN specifications for CString and 
non-floating-point values, since these can be conventient for 
those types as well.

*/

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

static void float_not_between_in_should_be_between_reports_failure();
static void float_between_in_should_be_between_reports_nothing();

static void cstring_not_between_in_should_be_between_reports_failure();
static void cstring_null_in_should_be_between_reports_failure();
static void cstring_between_in_should_be_between_reports_nothing();

static void float_between_in_should_not_be_between_reports_failure();
static void float_not_between_in_should_not_be_between_reports_nothing();

static void cstring_between_in_should_not_be_between_reports_failure();
static void cstring_not_between_in_should_not_be_between_reports_nothing();
static void cstring_null_in_should_not_be_between_reports_failure();

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

void run_between_comparison_specs()
{
	float_not_between_in_should_be_between_reports_failure();
	float_between_in_should_be_between_reports_nothing();

	cstring_not_between_in_should_be_between_reports_failure();
	cstring_null_in_should_be_between_reports_failure();
	cstring_between_in_should_be_between_reports_nothing();

	float_between_in_should_not_be_between_reports_failure();
	float_not_between_in_should_not_be_between_reports_nothing();

	cstring_between_in_should_not_be_between_reports_failure();
	cstring_not_between_in_should_not_be_between_reports_nothing();
	cstring_null_in_should_not_be_between_reports_failure();
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_not_between_in_should_be_between);

void Float_not_between_in_should_be_between::spec()
{
	VALUE( 112.5 ).SHOULD_BE_BETWEEN( 12.0, 13.0 ); // fail msg expected
	VALUE( 2.0 ).SHOULD_BE_BETWEEN( 2.0, 3.0 ); // fail msg - not "between" the two values
	VALUE( 6.0 ).SHOULD_BE_BETWEEN( 5.0, 6.0 ); // fail msg - not "between" the two values
	// see SHOULD_BE_WITHIN for a specification that includes the two values.
}

static void float_not_between_in_should_be_between_reports_failure()
{
	Float_not_between_in_should_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	{
		const std::string expected1("112.5 (112.5) should be between 12.0 (12) and 13.0 (13) FAILED in Float_not_between_in_should_be_between; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("2.0 (2) should be between 2.0 (2) and 3.0 (3) FAILED in Float_not_between_in_should_be_between; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("6.0 (6) should be between 5.0 (5) and 6.0 (6) FAILED in Float_not_between_in_should_be_between; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_between_in_should_be_between);

void Float_between_in_should_be_between::spec()
{
	VALUE( 12.5 ).SHOULD_BE_BETWEEN( 12.0, 13.0 ); // no msg expected
}

static void float_between_in_should_be_between_reports_nothing()
{
	Float_between_in_should_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_not_between_in_should_be_between);

void CString_not_between_in_should_be_between::spec()
{
	CSTRING_VALUE( "hhh" ).SHOULD_BE_BETWEEN( "bbb", "ccc" ); // fail msg expected
	CSTRING_VALUE( "ddd" ).SHOULD_BE_BETWEEN( "ddd", "eee" ); // fail msg - not "between" the two values
	CSTRING_VALUE( "ggg" ).SHOULD_BE_BETWEEN( "fff", "ggg" ); // fail msg - not "between" the two values
}

static void cstring_not_between_in_should_be_between_reports_failure()
{
	CString_not_between_in_should_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	{
		const std::string expected1("\"hhh\" (hhh) should be between \"bbb\" (bbb) and \"ccc\" (ccc) FAILED in CString_not_between_in_should_be_between; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("\"ddd\" (ddd) should be between \"ddd\" (ddd) and \"eee\" (eee) FAILED in CString_not_between_in_should_be_between; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("\"ggg\" (ggg) should be between \"fff\" (fff) and \"ggg\" (ggg) FAILED in CString_not_between_in_should_be_between; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_null_in_should_be_between);

void CString_null_in_should_be_between::spec()
{
	const char* nullStr = NULL;

	CSTRING_VALUE( nullStr ).SHOULD_BE_BETWEEN( "bbb", "ccc" ); // fail msg expected
	CSTRING_VALUE( "ddd" ).SHOULD_BE_BETWEEN( nullStr, "eee" ); // fail msg expected
	CSTRING_VALUE( "ggg" ).SHOULD_BE_BETWEEN( "fff", nullStr ); // fail msg expected
}

static void cstring_null_in_should_be_between_reports_failure()
{
	CString_null_in_should_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	{
		const std::string expected1("nullStr ((NULL POINTER)) should be between \"bbb\" (bbb) and \"ccc\" (ccc) FAILED in CString_null_in_should_be_between; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("\"ddd\" (ddd) should be between nullStr ((NULL POINTER)) and \"eee\" (eee) FAILED in CString_null_in_should_be_between; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("\"ggg\" (ggg) should be between \"fff\" (fff) and nullStr ((NULL POINTER)) FAILED in CString_null_in_should_be_between; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_between_in_should_be_between);

void CString_between_in_should_be_between::spec()
{
	CSTRING_VALUE( "bbb" ).SHOULD_BE_BETWEEN( "aaa", "ccc" ); // no msg expected
}

static void cstring_between_in_should_be_between_reports_nothing()
{
	CString_between_in_should_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_between_in_should_not_be_between);

void Float_between_in_should_not_be_between::spec()
{
	VALUE( 12.5 ).SHOULD_NOT_BE_BETWEEN( 12.0, 13.0 ); // fail msg expected
}

static void float_between_in_should_not_be_between_reports_failure()
{
	Float_between_in_should_not_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected("12.5 (12.5) should not be between 12.0 (12) and 13.0 (13) FAILED in Float_between_in_should_not_be_between; line");

	std::string::size_type foundPos = actual.find( expected, 0 );
	assert( foundPos != std::string::npos );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Float_not_between_in_should_not_be_between);

void Float_not_between_in_should_not_be_between::spec()
{
	VALUE( 120.5 ).SHOULD_NOT_BE_BETWEEN( 12.0, 13.0 ); // no msg expected
}

static void float_not_between_in_should_not_be_between_reports_nothing()
{
	Float_not_between_in_should_not_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_between_in_should_not_be_between);

void CString_between_in_should_not_be_between::spec()
{
	CSTRING_VALUE( "bbb" ).SHOULD_NOT_BE_BETWEEN( "aaa", "ccc" ); // fail msg expected
}

static void cstring_between_in_should_not_be_between_reports_failure()
{
	CString_between_in_should_not_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	
	const std::string expected("\"bbb\" (bbb) should not be between \"aaa\" (aaa) and \"ccc\" (ccc) FAILED in CString_between_in_should_not_be_between; line");

	std::string::size_type foundPos = actual.find( expected, 0 );
	assert( foundPos != std::string::npos );
}

/* this found a problem with AssertableValueCString::should_not_be_between_impl
  where it was comparing string pointers instead of converting to std::string 
  and comparing them. That can sometimes give a false "pass" if the string pointers
  happen to arranged in memory so the comparison of pointers has the same results
  as the desired value comparisons.

  consider replacing all conversions of std::string with strcmp() and maybe providing
  some way for the user to replace the comparison function with their own. (Override 
  a virtual function in Context, perhaps.)
*/

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_not_between_in_should_not_be_between);

void CString_not_between_in_should_not_be_between::spec()
{
	CSTRING_VALUE( "AAA" ).SHOULD_NOT_BE_BETWEEN( "BBB", "CCC" ); // no msg expected
}

static void cstring_not_between_in_should_not_be_between_reports_nothing()
{
	CString_not_between_in_should_not_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	const std::string expected;

	assert( expected == actual );
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(CString_null_in_should_not_be_between);

void CString_null_in_should_not_be_between::spec()
{
	const char* nullStr = NULL;

	CSTRING_VALUE( nullStr ).SHOULD_NOT_BE_BETWEEN( "bbb", "ccc" ); // fail msg expected
	CSTRING_VALUE( "ddd" ).SHOULD_NOT_BE_BETWEEN( nullStr, "eee" ); // fail msg expected
	CSTRING_VALUE( "ggg" ).SHOULD_NOT_BE_BETWEEN( "fff", nullStr ); // fail msg expected
}

static void cstring_null_in_should_not_be_between_reports_failure()
{
	CString_null_in_should_not_be_between context;
	
	RecordingFailuresSpecListener listener;
	context.run_spec( & listener );

	std::string actual = listener.logStr();
	{
		const std::string expected1("nullStr ((NULL POINTER)) should not be between \"bbb\" (bbb) and \"ccc\" (ccc) FAILED in CString_null_in_should_not_be_between; line");

		std::string::size_type foundPos1 = actual.find( expected1, 0 );
		assert( foundPos1 != std::string::npos );
	}
	{
		const std::string expected2("\"ddd\" (ddd) should not be between nullStr ((NULL POINTER)) and \"eee\" (eee) FAILED in CString_null_in_should_not_be_between; line");

		std::string::size_type foundPos2 = actual.find( expected2, 0 );
		assert( foundPos2 != std::string::npos );
	}
	{
		const std::string expected3("\"ggg\" (ggg) should not be between \"fff\" (fff) and nullStr ((NULL POINTER)) FAILED in CString_null_in_should_not_be_between; line");

		std::string::size_type foundPos3 = actual.find( expected3, 0 );
		assert( foundPos3 != std::string::npos );
	}
}

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
